#include <QDebug>
#include <QX11Info>

#include "dexpanelctlr.h"
#include "dexpanel.h"

DexPanelCtlr::DexPanelCtlr(int &argc, char **argv) : QApplication(argc, argv)
	{ init(); }

DexPanelCtlr::DexPanelCtlr(Display *dpy, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE colormap) : QApplication(dpy, argc, argv, visual, colormap)
	{ init(); }

DexPanelCtlr::~DexPanelCtlr()
	{
		foreach (DexPanel *panel, panels)
			delete panel;
	}

Atom DexPanelCtlr::getWindowType(Window win)
	{
		Atom windowType = None;
		Atom actualType;
		int format;
		unsigned long nitems, bytes_after;
		unsigned char *prop_return = NULL;

		if(XGetWindowProperty(QX11Info::display(),
							  win,
							  d_NET_WM_WINDOW_TYPE,
							  0L,
							  sizeof(Atom),
							  False,
							  XA_ATOM,
							  &actualType,
							  &format,
							  &nitems,
							  &bytes_after,
							  &prop_return) == Success)
		{
			windowType = *(Atom *)prop_return;
			XFree(prop_return);
		}

		return windowType;
	}

Window DexPanelCtlr::getActiveClient()
	{
		Window activeWin = None;
		Atom actualType;
		int format;
		unsigned long nitems, bytes_after;
		unsigned char *data = NULL;

		if(XGetWindowProperty(QX11Info::display(),
							  QX11Info::appRootWindow(QX11Info::appScreen()),
							  d_NET_ACTIVE_WINDOW,
							  0L,
							  sizeof(Window),
							  False,
							  XA_WINDOW,
							  &actualType,
							  &format,
							  &nitems,
							  &bytes_after,
							  &data) == Success)
		{
			activeWin = *(Window *)data;
			XFree(data);
		}

		return activeWin;
	}

void DexPanelCtlr::getActiveClients()
	{
		Atom type;
		int format;
		unsigned long nitems;
		unsigned long bytesRemaining;
		unsigned char *data;

		if (XGetWindowProperty(QX11Info::display(),
							   QX11Info::appRootWindow(QX11Info::appScreen()),
							   d_NET_CLIENT_LIST,
							   0L,
							   (~0L),
							   false,
							   AnyPropertyType,
							   &type,
							   &format,
							   &nitems,
							   &bytesRemaining,
							   &data) != Success)
			return;

		// Cast them to actual, usable window IDs.
		Window *clients = (Window *) data;
		for (unsigned int i = 0; i < nitems; i++)
		{
			// Make sure we don't pick up any panels, desktops, et cetera
			Atom windowType = getWindowType(clients[i]);
			if (windowType != d_NET_WM_WINDOW_TYPE_DESKTOP &&
				windowType != d_NET_WM_WINDOW_TYPE_DOCK &&
				windowType != d_KDE_NET_WM_WINDOW_TYPE_OVERRIDE)
			{
				foreach (DexPanel *panel, panels)
					panel->addClientButton(clients[i]);
				Window activeWindow = getActiveClient();
				foreach (DexPanel *panel, panels)
					panel->setButtonPressed(activeWindow, true);
			}
		}
		XFree(data);
	}

void DexPanelCtlr::handleClientMessage(XClientMessageEvent *event)
	{
//		qDebug() << "ClientMessage: "
//				 << XGetAtomName(QX11Info::display(), event->xclient.message_type)
//				 << event->xclient.window;
		foreach (DexPanel *panel, panels)
			panel->updateBtnText(event->window);

		if (event->message_type == d_NET_ACTIVE_WINDOW)
		{
			/** \todo
			 *
			 *	Keep track of active windows, or else clearPressedDown() will
			 *		remove the pressed down state of the active window button.
			 */
			// For when the window becomes the active window.
			foreach (DexPanel *panel, panels)
			{
				panel->clearPressedDown(); // Refer to TODO
				panel->setButtonPressed(event->window, true);
			}
		}

		// Too lazy to interpret window state change...
		else if (event->message_type == d_WM_CHANGE_STATE ||
				 event->message_type == d_NET_WM_STATE)
		{
			// For when window minimizes to panel.
			foreach (DexPanel *panel, panels)
			{
				panel->setButtonPressed(event->window, false);
//				panel->setButtonPressed(getActiveClient(), true);
			}
		}

		else if (event->message_type == d_NET_CLOSE_WINDOW)
		{
			foreach (DexPanel *panel, panels)
				panel->removeClientButton(event->window);
		}
	}

void DexPanelCtlr::handleDestroyNotify(XDestroyWindowEvent *event)
	{
		foreach (DexPanel *panel, panels)
			panel->removeClientButton(event->window);
	}

void DexPanelCtlr::handleMapNotify(XMapEvent *event)
	{
		// If xmap.event != xmap.window,
		//	then it becomes difficult to distinquish between a window
		//	and a non-window.
		if (event->event == event->window)
		{
			// Keeping track of override_redirect helps prevent any issues
			//	with QMenu and probably others.
			if (event->override_redirect != 1)
			{
//				qDebug() << "MapNotify: "
//						 << event->window;
				foreach (DexPanel *panel, panels)
					panel->addClientButton(event->window);
			}
		}
	}

void DexPanelCtlr::handlePropertyNotify(XPropertyEvent *event)
	{
//		qDebug() << "PropertyNotify: "
//				 << XGetAtomName(QX11Info::display(), event->atom)
//				 << event->window;
		// If the window manager notifies us of the currently opened windows,
		//	update the current list.
		if (event->atom == d_NET_CLIENT_LIST)
			getActiveClients();
	}

void DexPanelCtlr::handleReparentNotify(XReparentEvent *event)
	{
		// If xreparent.parent == xreparent.event,
		//	then it is possible that the window has been docked to systray,
		//	therefore no longer requiring a button.
		if (event->parent == event->event)
		{
			foreach (DexPanel *panel, panels)
				panel->removeClientButton(event->window);
		}

		// Otherwise, systray icon event could have been triggered,
		//	therefore generate a button for the window.
		else
		{
			// Checking for override redirect fixes an issue with Opera,
			//	and probably other, unknown issues.
			if (event->override_redirect == 0)
			{
				Atom windowType = getWindowType(event->window);
				qDebug() << "ReparentNotify: "
						 << event->window
//						 // Both the client and the kwin for the client have the same serial.
//						 << event->xreparent.serial
						 << XGetAtomName(QX11Info::display(), windowType);
				if (windowType != d_KDE_NET_WM_WINDOW_TYPE_OVERRIDE &&
					windowType == d_NET_WM_WINDOW_TYPE_NORMAL)
				{
					XSetWindowAttributes at;
					at.event_mask = PropertyChangeMask;
					XChangeWindowAttributes(QX11Info::display(), event->window, CWEventMask, &at);
					foreach (DexPanel *panel, panels)
					{
						panel->clearPressedDown();
						panel->addClientButton(event->window);
						panel->setButtonPressed(event->window, true);
					}
				}
			}
		}
	}

void DexPanelCtlr::handleUnmapNotify(XUnmapEvent *event)
	{
		foreach (DexPanel *panel, panels)
			panel->removeClientButton(event->window);
	}

void DexPanelCtlr::init()
	{
		/** \todo
		 *
		 *	Allow for multiple panels.
		*/
		DexPanel *panel = new DexPanel();
		panel->setPosition(DexPanel::BOTTOM);
//		panel->setWindowOpacity(50);
		panel->show();
		panels.insert(panels.count(), panel);

		d_WM_CHANGE_STATE = XInternAtom(QX11Info::display(), "WM_CHANGE_STATE", false);
		d_NET_WM_STATE = XInternAtom(QX11Info::display(), "_NET_WM_STATE", false);
		d_NET_ACTIVE_WINDOW = XInternAtom(QX11Info::display(), "_NET_ACTIVE_WINDOW", false);
		d_NET_CLOSE_WINDOW = XInternAtom(QX11Info::display(), "_NET_CLOSE_WINDOW", false);
		d_NET_CLIENT_LIST = XInternAtom(QX11Info::display(), "_NET_CLIENT_LIST", false);
		d_NET_WM_WINDOW_TYPE = XInternAtom(QX11Info::display(), "_NET_WM_WINDOW_TYPE", false);
		d_NET_WM_WINDOW_TYPE_NORMAL = XInternAtom(QX11Info::display(), "_NET_WM_WINDOW_TYPE_NORMAL", false);
		d_NET_WM_WINDOW_TYPE_DESKTOP = XInternAtom(QX11Info::display(), "_NET_WM_WINDOW_TYPE_DESKTOP", false);
		d_NET_WM_WINDOW_TYPE_DOCK = XInternAtom(QX11Info::display(), "_NET_WM_WINDOW_TYPE_DOCK", false);
		d_KDE_NET_WM_WINDOW_TYPE_OVERRIDE = XInternAtom(QX11Info::display(), "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE", false);

		// Get top-level windows and create buttons for them.
		getActiveClients();
	}

bool DexPanelCtlr::x11EventFilter(XEvent *event)
	{
//		qDebug() << event->type;
		switch (event->type)
		{
			case MapNotify:
				handleMapNotify(&event->xmap);
				break;
			// Due to the ambiguity of MapNotify, we have to consider ReparentNotify;
			//	that is, at least until i discover a better way.
			case ReparentNotify:
				handleReparentNotify(&event->xreparent);
				break;
			case UnmapNotify:
				handleUnmapNotify(&event->xunmap);
				break;
			case PropertyNotify:
				handlePropertyNotify(&event->xproperty);
				break;
			case ClientMessage:
				handleClientMessage(&event->xclient);
				break;
			case DestroyNotify:
				handleDestroyNotify(&event->xdestroywindow);
				break;
		}
		return false;
	}
